package com.emeraldparser.util;

import java.util.Collection;
import java.util.Set;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;

public class GraphAutomaton<V, E> extends ForwardingGraph<V, E> implements Automaton<V, E> {

	private final Graph<V, E> delegate;
	private final Set<V> startVertices = Sets.newHashSet();
	private final Set<V> acceptVertices = Sets.newHashSet();
	private final Set<V> rejectVertices = Sets.newHashSet();

	public GraphAutomaton(Graph<V, E> delegate) {
		this.delegate = delegate;
	}

	public static <V, E> GraphAutomaton<V, E> create() {
		return new GraphAutomaton<V, E>(SetGraph.<V, E>create());
	}

	public static <V, E> GraphAutomaton<V, E> create(Graph<V, E> delegate) {
		return new GraphAutomaton<V, E>(delegate);
	}

	@Override
	public Graph<V, E> delegate() {
		return delegate;
	}

	@Override
	public String toString() {
		return "Automaton[vertices=" + vertices() + ", startVertices=" + getStartVertices() + ", acceptVertices=" + getAcceptVertices() + ", rejectVertices=" + getRejectVertices() + ", edges=" + edges() + "]";
	}

	@Override
	public Collection<V> vertices() {
		return new StandardBulkModificationForwardingCollection<V>() {

			@Override
			protected Collection<V> delegate() {
				return GraphAutomaton.super.vertices();
			}

			@Override
			public boolean remove(Object object) {
				boolean modified = super.remove(object);
				startVertices.remove(object);
				acceptVertices.remove(object);
				rejectVertices.remove(object);
				return modified;
			}
			
		};
	}

	@Override
	public Collection<V> getStartVertices() {
		return new StandardBulkModificationForwardingCollection<V>() {

			@Override
			protected Collection<V> delegate() {
				return startVertices;
			}

			@Override
			public boolean add(V vertex) {
				Preconditions.checkArgument(containsVertex(vertex), "Automaton does not contain vertex %s", vertex);
				return super.add(vertex);
			}
			
		};
	}

	@Override
	public Collection<V> getAcceptVertices() {
		return new StandardBulkModificationForwardingCollection<V>() {

			@Override
			protected Collection<V> delegate() {
				return acceptVertices;
			}

			@Override
			public boolean add(V vertex) {
				Preconditions.checkArgument(containsVertex(vertex), "Automaton does not contain vertex %s", vertex);
				return super.add(vertex);
			}
			
		};
	}

	@Override
	public Collection<V> getRejectVertices() {
		return new StandardBulkModificationForwardingCollection<V>() {

			@Override
			protected Collection<V> delegate() {
				return rejectVertices;
			}

			@Override
			public boolean add(V vertex) {
				Preconditions.checkArgument(containsVertex(vertex), "Automaton does not contain vertex %s", vertex);
				return super.add(vertex);
			}
			
		};
	}

	@Override
	public boolean addStartVertex(V startVertex) {
		return startVertices.add(startVertex);
	}

	@Override
	public boolean addAcceptVertex(V acceptVertex) {
		return acceptVertices.add(acceptVertex);
	}

	@Override
	public boolean addRejectVertex(V rejectVertex) {
		return rejectVertices.add(rejectVertex);
	}

	@Override
	public int hashCode() {
		return Objects.hashCode(vertices(), edges(), getStartVertices(), getAcceptVertices(), getRejectVertices());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (!(obj instanceof Automaton<?, ?>)) {
			return false;
		}
		
		Automaton<?, ?> other = (Automaton<?, ?>) obj;
		
		return Objects.equal(vertices(), other.vertices())
		    && Objects.equal(edges(), other.edges())
		    && Objects.equal(getStartVertices(), other.getStartVertices())
		    && Objects.equal(getAcceptVertices(), other.getAcceptVertices())
		    && Objects.equal(getRejectVertices(), other.getRejectVertices());
	}

	@Override
	public boolean addAll(Automaton<? extends V, ? extends E> automaton) {
		// TODO Auto-generated method stub
		boolean modified = false;
		modified |= delegate.addAll(automaton);
		modified |= getStartVertices().addAll(automaton.getStartVertices());
		modified |= getAcceptVertices().addAll(automaton.getAcceptVertices());
		modified |= getRejectVertices().addAll(automaton.getRejectVertices());
		return modified;
	}

	@Override
	public boolean addGraph(Graph<? extends V, ? extends E> graph) {
		// TODO Auto-generated method stub
		return addAll(graph);
	}

	@Override
	public Collection<Path<V, E>> getPaths() {
		return Automatons.findPaths(this);
	}

}
